#ifndef     __GPIO_H
#define     __GPIO_H

#include "./rcc.h"

namespace stm32f1::gpio {

enum class Mode : uint8_t {
    INPUT_ANALOG = 0b0000,
    INPUT_FLOAT = 0b0100,
    INPUT_UP = 0b1000,
    INPUT_DOWN = 0b1100,

    OUT_10MHz_PP = 0b0001,
    OUT_2MHz_PP = 0b0010,
    OUT_50MHz_PP = 0b0011,

    OUT_10MHz_OD = 0b0101,
    OUT_2MHz_OD = 0b0110,
    OUT_50MHz_OD = 0b0111,

    OUT_10MHz_AF_PP = 0b1001,
    OUT_2MHz_AF_PP = 0b1010,
    OUT_50MHz_AF_PP = 0b1011,

    OUT_10MHz_AF_OD = 0b1101,
    OUT_2MHz_AF_OD = 0b1110,
    OUT_50MHz_AF_OD = 0b1111,
};

namespace inner {

template <uint8_t PIN, uint8_t VAL>
constexpr uint32_t cr_mask_by_pin() {
    if constexpr (PIN < 8) {
        return VAL << (PIN * 4);
    } else {
        return VAL << ((PIN - 8) * 4);
    }
}

template <typename GPIO, bool IS_LOW, typename PIN, typename... Pins>
constexpr uint32_t sum_cr_mask() {
    if constexpr ((IS_LOW == PIN::IS_LOW_VAL) && (GPIO::BASE_VAL == PIN::BASE_VAL)) {
        if constexpr (sizeof...(Pins) == 0) {
            return PIN::CR_MASK_VAL;
        } else {
            constexpr uint32_t NEXT = sum_cr_mask<GPIO, IS_LOW, Pins...>();
            static_assert((NEXT & PIN::CR_MASK_VAL) == 0x00, "invalid");
            return PIN::CR_MASK_VAL | NEXT;
        }
    } else {
        if constexpr (sizeof...(Pins) == 0) {
            return 0;
        } else {
            constexpr uint32_t NEXT = sum_cr_mask<GPIO, IS_LOW, Pins...>();
            return NEXT;
        }
    }
}

template <typename GPIO, bool IS_LOW, typename CFG, typename... Cfgs>
constexpr uint32_t sum_cr_mode() {
    if constexpr ((IS_LOW == CFG::IS_LOW_VAL) && (GPIO::BASE_VAL == CFG::BASE_VAL)) {
        if constexpr (sizeof...(Cfgs) == 0) {
            return CFG::CR_MODE_VAL;
        } else {
            return CFG::CR_MODE_VAL | sum_cr_mode<GPIO, IS_LOW, Cfgs...>();
        }
    } else {
        if constexpr (sizeof...(Cfgs) == 0) {
            return 0;
        } else {
            return sum_cr_mode<GPIO, IS_LOW, Cfgs...>();
        }
    }
}

template <typename GPIO, Mode MODE, typename CFG, typename... Cfgs>
constexpr uint32_t sum_odr_mode() {
    if constexpr ((GPIO::BASE_VAL == CFG::BASE_VAL) && (MODE == CFG::MODE_VAL)) {
        constexpr uint32_t MASK = 0x01 << CFG::PIN_VAL;
        if constexpr (sizeof...(Cfgs) == 0) {
            return MASK;
        } else {
            return MASK | sum_odr_mode<GPIO, MODE, Cfgs...>();
        }
    } else {
        if constexpr (sizeof...(Cfgs) == 0) {
            return 0;
        } else {
            return sum_odr_mode<GPIO, MODE, Cfgs...>();
        }
    }
}

template <Mode MODE>
constexpr uint8_t mode_value() {
    if constexpr (MODE == Mode::INPUT_DOWN) {
        return static_cast<uint8_t>(Mode::INPUT_UP);
    } else {
        return static_cast<uint8_t>(MODE);
    }
}

}

#define GPIO_INSTANCE(BASE)         reinterpret_cast<GPIO_TypeDef *>(BASE)

template <uint32_t BASE, uint32_t FIELD>
struct Gpio : public rcc::APB2Bus<FIELD> {

    static constexpr uint32_t BASE_VAL = BASE;
};

template <typename GPIO, uint8_t PIN>
struct Pin : public GPIO {

    static constexpr uint32_t PIN_VAL = PIN;
    static constexpr bool IS_LOW_VAL = PIN < 8;
    static constexpr uint32_t CR_MASK_VAL = inner::cr_mask_by_pin<PIN, 0x0F>();

    static inline void set() {
        GPIO_INSTANCE(GPIO::BASE_VAL)->BSRR = 0x01 << PIN_VAL;
    }

    static inline void clr() {
        GPIO_INSTANCE(GPIO::BASE_VAL)->BRR = 0x01 << PIN_VAL;
    }   

    static inline bool read() {
        return (GPIO_INSTANCE(GPIO::BASE_VAL)->IDR & (0x01 << PIN_VAL)) != 0;
    }

    template <Mode MODE>
    static inline void set_mode() {
        constexpr uint32_t CR_MODE_VAL = inner::cr_mask_by_pin<
                    PIN, 
                    inner::mode_value<MODE>()
                >();
        if constexpr (PIN < 8) {
            GPIO_INSTANCE(GPIO::BASE_VAL)->CRL = 
                GPIO_INSTANCE(GPIO::BASE_VAL)->CRL &
                (~CR_MASK_VAL) |
                CR_MODE_VAL;
        } else {
            GPIO_INSTANCE(GPIO::BASE_VAL)->CRH = 
                GPIO_INSTANCE(GPIO::BASE_VAL)->CRH &
                (~CR_MASK_VAL) |
                CR_MODE_VAL;
        }
        if constexpr (MODE == Mode::INPUT_UP) {
            set();
        } else if constexpr (MODE == Mode::INPUT_DOWN) {
            clr();
        }
    }
};

template <typename PIN, Mode MODE>
struct Cfg : public PIN {

    static constexpr Mode MODE_VAL = MODE;
    static constexpr uint32_t CR_MODE_VAL = inner::cr_mask_by_pin<
                PIN::PIN_VAL, 
                inner::mode_value<MODE>()
            >();
};

template <typename TARGET_GPIO, typename... Cfgs>
inline void config_once() {
	constexpr uint32_t BASE = TARGET_GPIO::BASE_VAL;
	
    constexpr uint32_t SUM_CRL_MASK = inner::sum_cr_mask<TARGET_GPIO, true, Cfgs...>();
    if constexpr (SUM_CRL_MASK != 0) {
        constexpr uint32_t SUM_CRL_MODE = inner::sum_cr_mode<TARGET_GPIO, true, Cfgs...>();
        GPIO_INSTANCE(BASE)->CRL = (GPIO_INSTANCE(BASE)->CRL & (~SUM_CRL_MASK)) | SUM_CRL_MODE;
    }

    constexpr uint32_t SUM_CRH_MASK = inner::sum_cr_mask<TARGET_GPIO, false, Cfgs...>();
    if constexpr (SUM_CRH_MASK != 0) {
        constexpr uint32_t SUM_CRH_MODE = inner::sum_cr_mode<TARGET_GPIO, false, Cfgs...>();
        GPIO_INSTANCE(BASE)->CRH = (GPIO_INSTANCE(BASE)->CRH & (~SUM_CRH_MASK)) | SUM_CRH_MODE;
    }

    constexpr uint32_t INPUT_UP_MASK = inner::sum_odr_mode<TARGET_GPIO, Mode::INPUT_UP, Cfgs...>();
    if constexpr (INPUT_UP_MASK != 0) {
        GPIO_INSTANCE(BASE)->BSRR = INPUT_UP_MASK;
    }

    constexpr uint32_t INPUT_DOWN_MASK = inner::sum_odr_mode<TARGET_GPIO, Mode::INPUT_DOWN, Cfgs...>();
    if constexpr (INPUT_DOWN_MASK != 0) {
        GPIO_INSTANCE(BASE)->BRR = INPUT_DOWN_MASK;
    }
}

using GpioA = Gpio<GPIOA_BASE, RCC_APB2ENR_IOPAEN>;
using GpioB = Gpio<GPIOB_BASE, RCC_APB2ENR_IOPBEN>;
using GpioC = Gpio<GPIOC_BASE, RCC_APB2ENR_IOPCEN>;
using GpioD = Gpio<GPIOD_BASE, RCC_APB2ENR_IOPDEN>;
using GpioE = Gpio<GPIOE_BASE, RCC_APB2ENR_IOPEEN>;
using GpioF = Gpio<GPIOF_BASE, RCC_APB2ENR_IOPFEN>;
using GpioG = Gpio<GPIOG_BASE, RCC_APB2ENR_IOPGEN>;

template <typename... Cfgs>
inline void config() {

    config_once<GpioA, Cfgs...>();
    config_once<GpioB, Cfgs...>();
    config_once<GpioC, Cfgs...>();
    config_once<GpioD, Cfgs...>();
    config_once<GpioE, Cfgs...>();
    config_once<GpioF, Cfgs...>();
    config_once<GpioG, Cfgs...>();
}

template <uint8_t PIN>
using PA = Pin<GpioA, PIN>;

template <uint8_t PIN>
using PB = Pin<GpioB, PIN>;

template <uint8_t PIN>
using PC = Pin<GpioC, PIN>;

template <uint8_t PIN>
using PD = Pin<GpioD, PIN>;

template <uint8_t PIN>
using PE = Pin<GpioE, PIN>;

template <uint8_t PIN>
using PF = Pin<GpioF, PIN>;

template <uint8_t PIN>
using PG = Pin<GpioG, PIN>;

}



#endif
